/**
 * 2021-5-24 添加“拉闸”机制。 （想法来源于并口同步电路数据传输中“下降沿触发数据传输”or“上升沿触发数据传输”机制）
 * 所谓“拉闸”机制（之后简称拉闸），就是在Channel内所有通道的数据在第一次到达之后立即向上通知。
 * 拉闸后，所有数据配合mixer(数据混合器)将数据制作出来
 */

/**
 * 2021-5-29 添加“阻塞”机制。
 * 该“阻塞”机制阻塞给定的端口，以便让对端Channel得知该端口需要限流。
 */
import { ListenOptions, Socket } from "net";
import PacketMixer from "../packethandler/PacketMixer";
import PacketPatcher from "../packethandler/PacketPatcher";
import Tunnel from "../tunnel/tunnel";

export default class Channel
{
    private tunnel: {tunnel:Tunnel, recvData: Buffer, recvPool: Buffer[], isOverflow: boolean}[];  //每一个通道包含的一个tunnel以及与tunnel协作支持处理问题的变量

    /**
     * 各种回调
     */
    private dataReciveCB: (refPort: number, data: Buffer) => void;
    private dataDrainCB: () => void;
    private allChannelConnectCB: () => void;
    private portChokeCB: (port: number) => void;
    private portUnChokedCB: (port: number) => void;
    private nullCall: () => void;

    private connectCounter: number; // tunnel通道连接成功计数器
    private initCounter:number; // tunnel监听成功计数器

    private dataPatcher: PacketPatcher; // 数据分发器
    private mixer: PacketMixer; // 数据合并器
    
    private nullBuffer: Buffer; // 空buffer，为了避免频繁制造空Buffer影响性能，固让一个空buffer赋值替代清除某一过期buffer的数据

    private recvChokeBuffer: {[index: string]: {data: Buffer[], resolveChokeTimer: NodeJS.Timeout, isChoked: boolean}}[];
    /**
     * 构造函数
     * @param tunnelN 通道数
     * @param autoReconnect 自动重连
     * @param type 服务器or客户端, true为服务器, false为客户端
     * @param loptions 监听选项，如果监听端口号为N，则建立数量为((N-1)+tunnelN)的通道
     */
    constructor(
        tunnelN: number,
        autoReconnect:boolean = true,
        type:boolean = true,
        options: any,
        initListenCallback: () => void)
    {
        this.tunnel = new Array<{tunnel: Tunnel, sendData: Buffer, recvData: Buffer, recvPool: Buffer[], isOverflow: boolean}>();
        this.mixer = new PacketMixer(tunnelN);
        this.connectCounter = 0;
        this.initCounter = 0;
        this.nullCall = () => {};
        this.dataPatcher = new PacketPatcher(tunnelN);
        this.nullBuffer = Buffer.alloc(0);
        this.recvChokeBuffer = new Array<{[index: string]: {data: Buffer[], resolveChokeTimer: NodeJS.Timeout, isChoked: boolean}}>();

        this.mixer.analyze((arg: number, data: Buffer) => {
            //分流整合后的数据

            if(this.recvChokeBuffer[arg.toString()] == undefined)
            {
                this.avoidNullCall(this.dataReciveCB)(arg, data);
                return;
            }else {
                this.recvChokeBuffer[arg.toString()].data.push(data);

                if(this.recvChokeBuffer[arg.toString()].data.length > 100)
                {
                    this.recvChokeBuffer[arg.toString()].isChoked = true;
                    this.avoidNullCall(this.portChokeCB)(arg);
                }
            }
        });

        //指定为服务器
        for(let i:number = 0; i < tunnelN; i++)
        {
            this.tunnel[i] = {
                tunnel: new Tunnel(type, { port: options.port + i, host: options.host }, () => {
                    this.initCounter++;
                    if(this.initCounter == tunnelN)
                    {
                        initListenCallback();
                    }
                }),
                recvData: this.nullBuffer,
                recvPool: new Array<Buffer>(),
                isOverflow: false
            };
            this.tunnel[i].tunnel.regConnected((socket: Socket) => {

                this.connectCounter++;
                if(this.connectCounter == tunnelN)
                {
                    this.avoidNullCall(this.allChannelConnectCB)();
                }
            });
            this.tunnel[i].tunnel.regBlockRecive((data: Buffer) => {
                if(this.tunnel[i].recvData.length > 0)
                {
                    //说明缓冲区有数据，应把此数据放入第二缓冲区
                    this.tunnel[i].recvPool.push(data);
                }else if(this.tunnel[i].recvData.length == 0)
                {
                    this.tunnel[i].recvData = data;
                }
                //this.mixer.input(data);
                if(this.ifAllChannelDataRecived()) {
                    this.pushChannelDataToMixer();
                }
            });
            this.tunnel[i].tunnel.regDataDrain(() => {
                this.tunnel[i].isOverflow = false;
                if(this.isChannelDrain())
                {
                    this.avoidNullCall(this.dataDrainCB)();
                }
            });
        }
    }
    /**
     * 注册Channel数据接收事件
     * @param cb Channel数据返回时调用
     */
    regDataRecive(cb: (refPort: number, data: Buffer) => void):Channel
    {
        this.dataReciveCB = cb;
        return this;
    }
    /**
     * 注册tunnel全部连接完毕事件
     * @param cb Channel内tunnel全部连接完毕时调用
     */
    regChannelAllConnected(cb: () => void):Channel
    {
        this.allChannelConnectCB = cb;
        return this;
    }
    /**
     * 注册缓冲区空事件
     * @param cb Channel数据缓冲区为空时调用
     */
    regChannelDrain(cb: () => void):Channel
    {
        this.dataDrainCB = cb;
        return this;
    }

    regPortChoke(cb: (port: number) => void): Channel
    {
        this.portChokeCB = cb;
        return this;
    }
    regPortUnChoked(cb: (port: number) => void): Channel
    {
        this.portUnChokedCB = cb;
        return this;
    }

    // pause()
    // {
    //     for(let i in this.tunnel)
    //     {
    //         this.tunnel[i].tunnel.getSocket().pause();
    //     }
    // }

    // resume()
    // {
    //     for(let i in this.tunnel)
    //     {
    //         this.tunnel[i].tunnel.getSocket().resume();
    //     }
    // }

    /**
     * 停止向上游应用程序端口发送数据，但不停止Channel的接收。
     * @param port 需要暂停传输的端口
     */
    pausePort(port: number) {
        if(this.recvChokeBuffer[port.toString()] == undefined)
        {
            this.recvChokeBuffer[port.toString()] = {
                data: new Array<Buffer>(),
                isChoked: false,
                resolveChokeTimer: undefined
            };
            return;
        }

        if(this.recvChokeBuffer[port.toString()].resolveChokeTimer != undefined)
        {
            clearInterval(this.recvChokeBuffer[port.toString()].resolveChokeTimer);
            this.recvChokeBuffer[port.toString()].resolveChokeTimer = undefined;
        }
    }

    /**
     * 恢复向上游应用程序端口发送数据。
     * @param port 需要恢复传输的端口
     */
    resumePort(port: number) {

        if(this.recvChokeBuffer[port.toString()] == undefined) return;
        if(this.recvChokeBuffer[port.toString()].resolveChokeTimer != undefined) return;

        this.recvChokeBuffer[port.toString()].resolveChokeTimer = setInterval(() => {
            let chokeData = this.recvChokeBuffer[port.toString()].data.shift();
            if(chokeData != undefined)
            {
                this.avoidNullCall(this.dataReciveCB)(port, chokeData);
            }else if(chokeData == undefined)
            {
                clearInterval(this.recvChokeBuffer[port.toString()].resolveChokeTimer);

                //this.recvChokeBuffer[port.toString()].resolveChokeTimer = undefined;
                if(this.recvChokeBuffer[port.toString()].isChoked == true)
                {
                    this.avoidNullCall(this.portUnChokedCB)(port);
                    this.recvChokeBuffer[port.toString()].isChoked = false;
                }

                this.recvChokeBuffer[port.toString()] = undefined;
            }
        }, 0);
    }

    clearPort(port: number) {
        if(this.recvChokeBuffer[port.toString()] == undefined) return;
        if(this.recvChokeBuffer[port.toString()].resolveChokeTimer != undefined)
        {
            clearInterval(this.recvChokeBuffer[port.toString()].resolveChokeTimer);
        }
        this.recvChokeBuffer[port.toString()].data = this.nullBuffer;
        this.recvChokeBuffer[port.toString()] = undefined;
    }

    /**
     * 发送数据
     * @param data 准备传输的数据
     * @param port 数据的来源端口
     * @returns 如果写缓冲区已满则返回false，等待dataDrain回调触发
     */
    sendData(data: Buffer, port: number):boolean {
        this.dataPatcher.input(data, port);
        let d:Buffer[] = this.dataPatcher.retPatchedPacket();
        
        let isAnyOverflow: boolean = false;
        for(let i in this.tunnel)
        {
            if(!this.tunnel[i].tunnel.writeBlock(d[i]))
            {
                this.tunnel[i].isOverflow = true;
                isAnyOverflow = true;
            }
        }
        return !isAnyOverflow;
    }


    public isChannelDrain(): boolean {
        for(let i in this.tunnel)
        {
            if(this.tunnel[i].isOverflow == true)
                return false;
        }
        return true;
    }

    private avoidNullCall(cb: any): any
    {
        if(cb != null){
            return cb;
        }
        return this.nullCall;
    }

    private ifAllChannelDataRecived()
    {
        for(let i in this.tunnel)
        {
            if(this.tunnel[i].recvData.length == 0)
            {
                return false;
            }
        }
        return true;
    }
    private pushChannelDataToMixer()
    {
        for(let i in this.tunnel)
        {
            this.mixer.input(this.tunnel[i].recvData);

            this.tunnel[i].recvData = this.nullBuffer;
            if(this.tunnel[i].recvPool.length > 0)
            {
                this.tunnel[i].recvData = this.tunnel[i].recvPool.shift();
            }
        }
    }

}